<?php
/**
 * Image.php
 *
 * 图像处理类
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://momoger.com/license
 *
 * @category	App
 * @package 	App_Util
 * @author 		Guya
 * @version    	1.0
 * @copyright  	Copyright © 2009 www.momoger.com All Rights Reserved.
 * @license    	http://momoger.com/license     New BSD License
 */

/**
 * 图片缩放， 添加水印操作类
 *
 * @category	App
 * @package 	App_Util
 * @copyright  	Copyright © 2009 www.momoger.com All Rights Reserved.
 * @license    	http://momoger.com/license     New BSD License
 */
class App_Util_Image {

	/**
	 * 程序处理过程中异常信息.
	 *
	 * @var string
	 */
	private $msg = NULL;

	/**
	 * 源图片的信息.
	 *
	 * @var array
	 */
	private $targetInfo = array();

	/**
	 * 新生成图片的信息.
	 *
	 * @var array
	 */
	private $destInfo = array();

	/**
	 * 新生成图片空白部分的填充颜色.
	 * 默认为白色(R: 255, G: 255, B: 255)
	 * 黑色(R: 0, G: 0, B: 0)
	 *
	 * @var array
	 */
	private $bgColor = array('r' => 255, 'g' => 255, 'b' => 255);

	/**
	 * 图片格式定义常数.
	 *
	 * @var array
	 */
	private $imageTypes = array(
		1 => 'GIF',
		2 => 'JPG',
		3 => 'PNG',
		4 => 'SWF',
		5 => 'PSD',
		6 => 'BMP',
		7 => 'TIFF',
		8 => 'TIFF',
		9 => 'JPC',
		10 => 'JP2',
		11 => 'JPX',
		12 => 'JB2',
		13 => 'SWC',
		14 => 'IFF',
		15 => 'WBMP',
		16 => 'XBM'
	);

	/**
	 * 设置缩放图超过原图时保持原样图片.
	 *
	 * @var boolean
	 */
	private $justZoomOut = TRUE;

	/**
	 * 构造函数.
	 *
	 * @param string $target 源图片地址, 参考图片地址.
	 * @param string $dest 新图片地址, 缩放后的心图片保存地址.
	 * @param string $type 新图片格式.
	 * @param int $width 新图片宽度.
	 * @param int $height 新图片高度.
	 * @param int $quality 新图片质量, 只对JPG格式图片有效果.
	 * @param boolean $justZoomOut 对放大的图片保持在新图片中居中不放大. 默认为居中不放大.
	 */
	public function __construct($target = NULL, $dest = NULL, $type = NULL, $width = 0, $height = 0, $quality = 95, $justZoomOut = TRUE) {
		if ($target) {
			$this->setImage($target, $dest);
		}
		if (!$this->msg) {
			$this->setZoomedImageType($type);
			$this->setZoomedImageSize($width, $height);
		}
		$this->setZoomedImageQuality($quality);
		$this->setJustZoomOut($justZoomOut);
	}

	/**
	 * 设置图片的源地址, 缩放后的图片保存地址.
	 * 如果未设置缩放后的保存地址, 默认保存到与源图片相同的地方.
	 * 文件名为: zoom_源图片名.
	 *
	 * @param string $target 图片源地址.
	 * @param string $dest 新的图片地址.
	 * @return ImageZoom
	 */
	public function setImage($target, $dest = NULL) {
		if (!file_exists($target)) {
			$this->msg = '源图片文件不存在! 无法创建新图片文件!';
		} else {
			if ($target == $dest) {
				$this->msg = '缩放图片不能和源文件保存位置相同!';
			} else {
				$img = @getimagesize($target);
				if (!$img || !(int)$img[0] || !(int)$img[1]) {
					$this->msg = '源图片文件是一个无效的图片文件! 无法创建新图片文件!';
				} else {

					$this->targetInfo['uri'] = $target;
					$this->targetInfo['type'] = @$this->imageTypes[$img[2]];
					$this->targetInfo['width'] = $img[0];
					$this->targetInfo['height'] = $img[1];
					$this->targetInfo['size'] = @filesize($target);

					if (!$dest) {
						$this->destInfo['uri'] = dirname($target) . DIRECTORY_SEPARATOR . 'zoom_' . basename($target);
					} else {
						$this->destInfo['uri'] = $dest;
					}
					$this->destInfo['type'] = $this->targetInfo['type'];
					$this->destInfo['size'] = 0;
				}
			}
		}
		return $this;
	}

	/**
	 * 设置缩放后的图片格式/类型.
	 * 默认保持和源文件类型一致.
	 * 该方法需要在 setImage() 后调用才能生效. 否则返回的新图片类型信息可能不准确.
	 *
	 * @param string $type 指定要生成的图像格式.
	 * @return ImageZoom
	 */
	public function setZoomedImageType($type = NULL) {
		if ($type) {
			$this->destInfo['type'] = strtoupper($type);
		}
		return $this;
	}

	/**
	 * 设定新图片的尺寸. 该方法使用前需要先调用 setImage() 方法.
	 *
	 * @param int $w 新图片宽度.
	 * @param int $h 新图片高度.
	 * @return ImageZoom
	 */
	public function setZoomedImageSize($w = 0, $h = 0) {
		$w = (int)$w;
		$h = (int)$h;
		if (!$w && !$h) {
			$this->msg = '新图像未设定尺寸, 无法创建新图片文件!';
		} else {
			if ($w && !$h) {
				$this->destInfo['width'] = $w;
				$this->destInfo['height'] = intval(($this->targetInfo['height'] * $w) / $this->targetInfo['width']);
			} else if($h && !$w) {
				$this->destInfo['height'] = $h;
				$this->destInfo['width'] = intval(($this->targetInfo['width'] * $h) / $this->targetInfo['height']);
			} else {
				$this->destInfo['width'] = $w;
				$this->destInfo['height'] = $h;
			}
		}
		return $this;
	}

	/**
	 * 设置缩放图像的背景颜色. 画布颜色. RGB颜色设定模式.
	 *
	 * @param int $r 红色颜色值, 0 - 255 之间.
	 * @param int $g 绿色颜色值, 0 - 255 之间.
	 * @param int $b 蓝色颜色值, 0 - 255 之间.
	 * @return ImageZoom
	 */
	public function setZoomedImageBgColor($red = 0, $green = 0, $blue = 0) {
		$red = (int)$red;
		$green = (int)$green;
		$blue = (int)$blue;
		if ($red >= 0 && $red <= 255) {
			$this->bgColor['r'] = $red;
		}
		if ($green >= 0 && $green <= 255) {
			$this->bgColor['g'] = $green;
		}
		if ($blue >= 0 && $blue <= 255) {
			$this->bgColor['b'] = $blue;
		}
		return $this;
	}

	/**
	 * 设置图片缩放质量, 仅对JPG模式缩放有效果.
	 *
	 * @param int $quality 图片缩放质量指标.
	 * @return ImageZoom
	 */
	public function setZoomedImageQuality($quality = 90) {
		$quality = (int)$quality;
		if ($quality > 10) {
			$this->destInfo['quality'] = $quality;
		}
		return $this;
	}

	/**
	 * 设置是否仅缩放图比源图大时保持原样,
	 * 仅在图片缩小时缩放原图到新图.
	 *
	 * @param boolean $justZoomOut 只启用缩小标志.
	 * @return ImageZoom
	 */
	public function setJustZoomOut($justZoomOut = true) {
		$this->destInfo['justZoomOut'] = (bool)$justZoomOut;
		return $this;
	}

	/**
	 * 设置给图片加水印图片.
	 * 建议使用透明的PNG格式图片, 增强效果.
	 *
	 * 位置信息为: 0: 正中心. 1: 左上角. 2: 右上角. 3: 右下角. 4: 左下角.
	 *
	 * @param string $imgUri 水印图片地址.
	 * @param int $position 添加到目标图片位置.
	 * @return ImageZoom
	 */
	public function setWaterImage($imgUri, $position = 3) {
		if (file_exists($imgUri)) {
			$img = @getimagesize($imgUri);
			if (is_array($img) && count($img)) {
				$this->destInfo['waterImageUri'] = $imgUri;
				$this->destInfo['waterImageType'] = @$this->imageTypes[$img[2]];
				$this->destInfo['waterImageWidth'] = $img[0];
				$this->destInfo['waterImageHeight'] = $img[1];
				$this->destInfo['waterImagePosition'] = (int)$position;
			}
		}
		return $this;
	}

	/**
	 * 获取源图片文件信息. 信息以数组模式返回.
	 * 键值说明:
	 * uri: 图片的实际地址.
	 * type: 图片的类型.
	 * width: 图片的宽度.
	 * height: 图片的高度.
	 * size: 图片的大小.
	 *
	 * @return array
	 */
	public function getImageInfo() {
		return $this->targetInfo;
	}

	/**
	 * 获取缩放后图片文件信息. 信息以数组模式返回.
	 * 键值说明:
	 * uri: 图片的实际地址.
	 * type: 图片的类型.
	 * width: 图片的宽度.
	 * height: 图片的高度.
	 * size: 图片的大小.
	 *
	 * @return array
	 */
	public function getZoomedImageInfo() {
		return $this->destInfo;
	}

	/**
	 * 对图片进行缩放操作. 成功返回TRUE, 失败返回FALSE.
	 *
	 * @return boolean
	 */
	public function zoomImage() {
		if ($this->msg) {
			return FALSE;
		}

		//图像类型检查.
		$allowTypes = array('JPG', 'GIF', 'PNG');
		if (!in_array($this->targetInfo['type'], $allowTypes) || !in_array($this->destInfo['type'], $allowTypes)) {
			$this->msg = '本程序仅支持操作 JPG, GIF, PNG 格式图片!';
			return FALSE;
		}

		//环境检查
		if (!function_exists('imagecreatetruecolor')) {
			$this->msg = '本程序需要服务器安装GD2扩展. 推荐使用2.0.28以上版本!';
			return FALSE;
		}

		//新图片默认绘图位置
		$dst = array(
			'x'	=>	0,
			'y'	=>	0,
			'w'	=>	$this->destInfo['width'],
			'h'	=>	$this->destInfo['height']
		);
		$dstScale = $dst['w']/$dst['h'];

		//源图片默认读取位置
		$src = array(
			'x'	=>	0,
			'y'	=>	0,
			'w'	=>	$this->targetInfo['width'],
			'h'	=>	$this->targetInfo['height']
		);
		$srcScale = $src['w']/$src['h'];

		//调整位置信息. 使用居中模式
		if ($dst['w'] >= $src['w'] && $dst['h'] >= $src['h']) { //新图片尺寸比源图片尺寸要大.
			if ($this->targetInfo['justZoomOut']) {	//保持原图在新图居中位置, 不放大.
				$dst['x'] = intval(($dst['w'] - $src['w'])/2);
				$dst['y'] = intval(($dst['y'] - $src['y'])/2);
				$dst['w'] = $src['w'];
				$dst['h'] = $dst['h'];
			} else {	//图片放大模式.
				if ($dstScale >= $srcScale) {	//纵向放大模式.
					$scaledWidth = intval(($dst['h'] * $src['w']) / $src['h']);
					$dst['x'] = intval(($dst['w'] - $scaledWidth) / 2);
					$dst['w'] = $scaledWidth;
				} else {	//横向放大模式.
					$scaledHeight = intval(($src['h'] * $dst['w']) / $src['w']);
					$dst['y'] = intval(($dst['h'] - $scaledHeight) / 2);
					$dst['h'] = $scaledHeight;
				}
			}
		}
		/**
		else if ($dst['w'] >= $src['w'] && $dst['h'] < $src['h']) {
			$zoomOutWidth = intval(($src['w'] * $dst['h']) / $src['h']);
			$dst['x'] = intval(($dst['w'] - $zoomOutWidth) / 2);
			$dst['w'] = $zoomOutWidth;
		} else if ($dst['w'] < $src['w'] && $dst['h'] >= $src['h']) {
			$zoomOutHeight = intval(($dst['w'] * $src['h']) / $src['w']);
			$dst['y'] = intval(($dst['h'] - $zoomOutHeight) / 2);
			$dst['h'] = $zoomOutHeight;
		}
		*/
		else {
			if ($dstScale >= $srcScale) {
				$zoomOutWidth = intval(($src['w'] * $dst['h']) / $src['h']);
				$dst['x'] = intval(($dst['w'] - $zoomOutWidth) / 2);
				$dst['w'] = $zoomOutWidth;
			} else {
				$zoomOutHeight = intval(($dst['w'] * $src['h']) / $src['w']);
				$dst['y'] = intval(($dst['h'] - $zoomOutHeight) / 2);
				$dst['h'] = $zoomOutHeight;
			}
		}

		//创建画布.
		$zoomImg = imagecreatetruecolor($this->destInfo['width'], $this->destInfo['height']);

		//设置画布颜色
		$zoomImgBgColor = imagecolorallocate($zoomImg, $this->bgColor['r'], $this->bgColor['g'], $this->bgColor['b']);
		if ( -1 == $zoomImgBgColor) {
			imagedestroy($zoomImg);
			$this->msg = '缩放图背景颜色设置失效! 未能创建缩放图!';
			return FALSE;
		}

		//填充画布颜色
		if (!imagefill($zoomImg, 0, 0, $zoomImgBgColor)) {
			imagedestroy($zoomImg);
			$this->msg = '缩放图背景颜色绘制失败! 未能创建缩放图!';
			return FALSE;
		}

		//读取源图片数据.
		$sourceImg = NULL;
		switch ($this->targetInfo['type']) {
			case 'GIF':
				$sourceImg = @imagecreatefromgif($this->targetInfo['uri']);
				break;
			case 'JPG':
				$sourceImg = @imagecreatefromjpeg($this->targetInfo['uri']);
				break;
			case 'PNG':
				$sourceImg = @imagecreatefrompng($this->targetInfo['uri']);
				break;
			default:
				break;
		}
		if(!$sourceImg) {
			imagedestroy($zoomImg);
			$this->msg = '参考源图片读取失败! 未能创建缩放图!';
			return FALSE;
		}

		//缩放图片
		if(!imagecopyresized($zoomImg, $sourceImg, $dst['x'], $dst['y'], $src['x'], $src['y'], $dst['w'], $dst['h'], $src['w'], $src['h'])) {
			imagedestroy($zoomImg);
			imagedestroy($sourceImg);
			$this->msg = '参考源图片复制失败! 未能创建缩放图!';
			return FALSE;
		}

		//添加水印图片.
		if (isset($this->destInfo['waterImageType']) && $this->destInfo['waterImageType']) {
			//确认添加水印条件.
			if ($this->destInfo['waterImageWidth'] <= $this->destInfo['width'] && $this->destInfo['waterImageHeight'] <= $this->destInfo['height']) {
				//计算水印图片位置.
				$p = array('x' => 0, 'y' => 0);
				switch ($this->destInfo['waterImagePosition']) {
					case 1:	//左上角
						break;
					case 2:	//右上角
						$p['x'] = $this->destInfo['width'] - $this->destInfo['waterImageWidth'];
						break;
					case 3:	//右下角
						$p['x'] = $this->destInfo['width'] - $this->destInfo['waterImageWidth'];
						$p['y'] = $this->destInfo['height'] - $this->destInfo['waterImageHeight'];
						break;
					case 4:	//左下角
						$p['y'] = $this->destInfo['height'] - $this->destInfo['waterImageHeight'];
						break;
					case 5:	//正中央
						$p['x'] = intval(($this->destInfo['width'] - $this->destInfo['waterImageWidth'])/2);
						$p['y'] = intval(($this->destInfo['height'] - $this->destInfo['waterImageHeight'])/2);
						break;
					default:
						break;
				}

				//生陈水印图片资源.
				$copyImg = NULL;
				switch ($this->destInfo['waterImageType']) {
					case 'GIF':
						$copyImg = @imagecreatefromgif($this->destInfo['waterImageUri']);
						break;
					case 'JPG':
						$copyImg = @imagecreatefromjpeg($this->destInfo['waterImageUri']);
						break;
					case 'PNG':
						$copyImg = @imagecreatefrompng($this->destInfo['waterImageUri']);
						break;
					default:
						break;
				}

				//实施拷贝水印图片.
				if($copyImg) {
					imagecopy($zoomImg, $copyImg, $p['x'], $p['y'], 0, 0, $this->destInfo['waterImageWidth'], $this->destInfo['waterImageHeight']);
					imagedestroy($copyImg);
				}
			}
		}

		//保存图片
		$saveOk = FALSE;
		switch ($this->destInfo['type']) {
			case 'GIF':
				$saveOk = imagegif($zoomImg, $this->destInfo['uri']);
				break;
			case 'JPG':
				$saveOk = imagejpeg($zoomImg, $this->destInfo['uri'], $this->destInfo['quality']);
				break;
			case 'PNG':
				$saveOk = imagepng($zoomImg, $this->destInfo['uri']);
				break;
			default:
				break;
		}

		//销毁资源, 清理内存.
		imagedestroy($zoomImg);
		imagedestroy($sourceImg);

		//检验图片
		if ($saveOk) {
			$this->destInfo['size'] = filesize($this->destInfo['uri']);
		} else {
			$this->msg = '缩放图保持到本地文件失败!';
		}

		//返回结果.
		if ((int)$this->destInfo['size']) {
			return TRUE;
		} else {
			return FALSE;
		}
	}

	/**
	 * 获取异常信息.
	 *
	 * @return string
	 */
	public function getMsg() {
		return $this->msg;
	}
}